#include <iostream>
#include <sstream>

#include <typeinfo>


#include <cppunit/TestCase.h>
#include <cppunit/TestFixture.h>
#include <cppunit/TestCaller.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestSuite.h>

#include <cppunit/ui/text/TestRunner.h>

#include "PlantainLexer.hpp"
#include "PlantainTokenTypes.hpp"

using namespace std;

class SimpleLexerTest : public CppUnit::TestFixture{
public:
  void setUp(){}
  void tearDown(){}
protected:
  void testDummy(){}

  void testFloat(){
    stringstream *ss = new stringstream(stringstream::in | stringstream::out);
    PlantainLexer *pl = new PlantainLexer(*ss);
    antlr::RefToken token;
    *ss << "123.234 /*comment is * /*here */";
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::FLOAT, (int)token->getType());
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::EOF_, (int)token->getType());
    delete pl;
    delete ss;
  }

  void testInt(){
    stringstream *ss = new stringstream(stringstream::in | stringstream::out);
    PlantainLexer *pl = new PlantainLexer(*ss);
    antlr::RefToken token;
    *ss << "123";
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::INT, (int)token->getType());
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::EOF_, (int)token->getType());
    delete pl;
    delete ss;
  }

  void testDot(){
    stringstream *ss = new stringstream(stringstream::in | stringstream::out);
    PlantainLexer *pl = new PlantainLexer(*ss);
    antlr::RefToken token;
    *ss << "foo.bar\n";
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::DOT, (int)token->getType());
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::EOF_, (int)token->getType());
    delete pl;
    delete ss;
  }


  void testAssign(){
    stringstream *ss = new stringstream(stringstream::in | stringstream::out);
    PlantainLexer *pl = new PlantainLexer(*ss);
    antlr::RefToken token;
    *ss << "=";
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ASSIGN, (int)token->getType());
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::EOF_, (int)token->getType());
    delete pl;
    delete ss;
  }

  void testMLComment(){
    stringstream *ss = new stringstream(stringstream::in | stringstream::out);
    PlantainLexer *pl = new PlantainLexer(*ss);
    antlr::RefToken token;
    *ss << "123.234 /*comment is * \n/*here */";
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::FLOAT, (int)token->getType());
    token = pl->nextToken();
    CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::EOF_, (int)token->getType());
    delete pl;
    delete ss;
  }

  void testSLComment(){
    stringstream *ss = new stringstream(stringstream::in | stringstream::out);
    PlantainLexer *pl = new PlantainLexer(*ss);
    antlr::RefToken token;
    *ss << "123.234 //Comment /*\n 456 // */  ";
    try{
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::FLOAT, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::INT, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::EOF_, (int)token->getType());
    }catch(antlr::RecognitionException ex){
      CPPUNIT_FAIL(ex.getFileLineColumnString());
    }catch(antlr::ANTLRException ex){
      CPPUNIT_FAIL(ex.toString());
    }
    delete pl;
    delete ss;
  }
  
  void testAnd(){
    stringstream *ss = new stringstream(stringstream::in | stringstream::out);
    PlantainLexer *pl = new PlantainLexer(*ss);
    antlr::RefToken token;
    *ss << "true and false";
    try{
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::KW_and, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
              CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::EOF_, (int)token->getType());
    }catch(antlr::RecognitionException ex){
      CPPUNIT_FAIL(ex.getFileLineColumnString());
    }catch(antlr::ANTLRException ex){
      CPPUNIT_FAIL(ex.toString());
    }
    delete pl;
    delete ss;
  }


  void testMacroSimple(){
    stringstream *ss = new stringstream(stringstream::in | stringstream::out);
    PlantainLexer *pl = new PlantainLexer(*ss);
    antlr::RefToken token;
    *ss << "install for foo\n";
    *ss << "for range(0,10) as x {}";
    try{
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::KW_install, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::MACRO, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::LPAREN, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::INT, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::COMMA, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::INT, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::RPAREN, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::KW_as, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::LCURLY, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::RCURLY, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::EOF_, (int)token->getType());
    }catch(antlr::RecognitionException ex){
      CPPUNIT_FAIL(ex.getFileLineColumnString());
    }catch(antlr::ANTLRException ex){
      CPPUNIT_FAIL(ex.toString());
    }
    delete pl;
    delete ss;
  }

  void checkInstallLine(PlantainLexer *pl){
    antlr::RefToken token;
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::KW_install, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::SEMI, (int)token->getType());
  }


  void testMacroComplex(){
    stringstream *ss = new stringstream(stringstream::in | stringstream::out);
    PlantainLexer *pl = new PlantainLexer(*ss);
    antlr::RefToken token;
    *ss << "install for foo;\n";
    *ss << "for range(0,10) as x {\n";
    *ss << "  install while bar;\n";
    *ss << "  while x y;\n";
    *ss << "  for list as y{};\n";
    *ss << "};\n";
    *ss << "while y z;\n";
    try{

      //install for foo;
      checkInstallLine(pl);
      //for range(0,10) as x{
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::MACRO, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::LPAREN, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::INT, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::COMMA, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::INT, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::RPAREN, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::KW_as, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::LCURLY, (int)token->getType());
      //install while bar;
      checkInstallLine(pl);

      //while x y;
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::MACRO, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::SEMI, (int)token->getType());

      //for list as y{};
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::MACRO, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::KW_as, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::LCURLY, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::RCURLY, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::SEMI, (int)token->getType());
      
      //};
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::RCURLY, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::SEMI, (int)token->getType());

      //while y z;
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::SEMI, (int)token->getType());

      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::EOF_, (int)token->getType());
    }catch(antlr::RecognitionException ex){
      CPPUNIT_FAIL(ex.getFileLineColumnString());
    }catch(antlr::ANTLRException ex){
      CPPUNIT_FAIL(ex.toString());
    }
    delete pl;
    delete ss;
  }


  void testOr(){
    stringstream *ss = new stringstream(stringstream::in | stringstream::out);
    PlantainLexer *pl = new PlantainLexer(*ss);
    antlr::RefToken token;
    *ss << "true or false";
    try{
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::KW_or, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::ID, (int)token->getType());
      token = pl->nextToken();
      CPPUNIT_ASSERT_EQUAL((int)PlantainTokenTypes::EOF_, (int)token->getType());
    }catch(antlr::RecognitionException ex){
      CPPUNIT_FAIL(ex.getFileLineColumnString());
    }catch(antlr::ANTLRException ex){
      CPPUNIT_FAIL(ex.toString());
    }
    delete pl;
    delete ss;
  }


public:

  static CppUnit::Test* suite(){
    CppUnit::TestSuite *suite = new CppUnit::TestSuite("SimpleLexerTest");
    suite->addTest( new CppUnit::TestCaller<SimpleLexerTest>("testFloat", 
                                                             &SimpleLexerTest::testFloat));
    suite->addTest( new CppUnit::TestCaller<SimpleLexerTest>("testInt", 
                                                             &SimpleLexerTest::testInt));
    suite->addTest( new CppUnit::TestCaller<SimpleLexerTest>("testDot", 
                                                             &SimpleLexerTest::testDot));
    suite->addTest( new CppUnit::TestCaller<SimpleLexerTest>("testAssign", 
                                                             &SimpleLexerTest::testAssign));
    suite->addTest( new CppUnit::TestCaller<SimpleLexerTest>("testMLComment", 
                                                             &SimpleLexerTest::testMLComment));
    suite->addTest( new CppUnit::TestCaller<SimpleLexerTest>("testSLComment", 
                                                             &SimpleLexerTest::testSLComment));
    suite->addTest( new CppUnit::TestCaller<SimpleLexerTest>("testAnd", 
                                                             &SimpleLexerTest::testAnd));
    suite->addTest( new CppUnit::TestCaller<SimpleLexerTest>("testOr", 
                                                             &SimpleLexerTest::testOr));
    suite->addTest( new CppUnit::TestCaller<SimpleLexerTest>("testMacroSimple", 
                                                             &SimpleLexerTest::testMacroSimple));
    suite->addTest( new CppUnit::TestCaller<SimpleLexerTest>("testMacroComplex", 
                                                             &SimpleLexerTest::testMacroComplex));
    return suite;
  }
};
